#include <linux/module.h>
#include <linux/pci.h>
#include "drv_ecum.h"
#include "drv_pcie.h"
#include "drv_cdev.h"
#include "drv_xcan.h"

#define CANFD_DRV_NAME      "pcie_canfd"
#define CANFD_DEV_NUM       3
#define CAN_DRV_NAME        "pcie_can"
#define CAN_DEV_NUM         6

#if (HARD_VER == 0)
#define ECUM_CAN_DEV_NUM            8                       /* CAN 设备数量 */
#define ECUM_CANFD_DEV_NUM          1                       /* CANFD 设备数量 */
#elif (HARD_VER == 1)
#define ECUM_CAN_DEV_NUM            8                       /* CAN 设备数量 */
#define ECUM_CANFD_DEV_NUM          2                       /* CANFD 设备数量 */
#endif
#define ECUM_CAN_DRV_NAME           "pcie_can"              /* CAN 设备名称 */
#define ECUM_CANFD_DRV_NAME         "pcie_canfd"            /* CANFD 设备名称 */

struct cdev_dev_t ecum_can_cdev[ECUM_CAN_DEV_NUM];          /* ECU 模块 CAN 字符设备 */
struct xcan_dev_t ecum_can_dev[ECUM_CAN_DEV_NUM];           /* ECU 模块 CAN 设备 */
struct cdev_dev_t ecum_canfd_cdev[ECUM_CANFD_DEV_NUM];      /* ECU 模块 CANFD 字符设备 */
struct xcan_dev_t ecum_canfd_dev[ECUM_CANFD_DEV_NUM];       /* ECU 模块 CANFD 设备 */

/* PCIe ECU 模块 */
struct pcie_ecum_t {
    struct device *dev;                 /* Linux 设备指针 */
    struct pcie_dev_t pcie_dev;         /* PCIe 设备 */
    struct xcan_ctrl_t xcan_ctrl;       /* CAN 控制器 */
    struct xcan_ctrl_t xcanfd_ctrl;     /* CANFD 控制器 */
};

static int pcie_ecum_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
    int ret = 0;
    struct pcie_ecum_t *pmcdev = NULL;

    dev_info(&pdev->dev, "probing %04x:%04x", pdev->vendor, pdev->device);
    /* 检查设备是否已被占用 */
    if (pci_get_drvdata(pdev)) {
        dev_warn(&pdev->dev, "device already claimed");
        return -EBUSY;
    }

    /* 分配设备结构内存 */
    pmcdev = devm_kzalloc(&pdev->dev, sizeof(*pmcdev), GFP_KERNEL);
    if (!pmcdev)
        return -ENOMEM;

    pmcdev->dev = &pdev->dev;
    pci_set_drvdata(pdev, pmcdev);

    /* 初始化 PCIe 设备 */
    ret = init_pcie_dev(pdev, &pmcdev->pcie_dev);
    if (ret) {
        dev_err(pmcdev->dev, "Init PCIe device failed");
        goto err_cleanup;
    }

    /* 初始化 CAN 控制器 */
    ret = init_xcan_ctrl(&pmcdev->pcie_dev, ecum_can_cdev, &pmcdev->xcan_ctrl, 
                         ecum_can_dev, ECUM_CAN_DRV_NAME, ECUM_CAN_DEV_NUM, &xcan_axi_data);
    if (ret < 0) {
        dev_err(pmcdev->dev, "Init CANFD device failed");
        goto err_cleanup;
    }
    dev_info(pmcdev->dev, "Init CANFD device success");

    /* 初始化 CAN FD 控制器 */
    ret = init_xcan_ctrl(&pmcdev->pcie_dev, ecum_canfd_cdev, &pmcdev->xcanfd_ctrl, 
                         ecum_canfd_dev, ECUM_CANFD_DRV_NAME, ECUM_CANFD_DEV_NUM, &xcan_canfd2_data);
    if (ret < 0) {
        dev_err(pmcdev->dev, "Init CANFD device failed");
        goto err_cleanup;
    }
    dev_info(pmcdev->dev, "Init CANFD device success");
    dev_info(&pdev->dev, "PCIe %s device probed", ECUM_DRV_NAME);

    return 0;

err_cleanup:
    pci_set_drvdata(pdev, NULL);
    return ret;
}

static void pcie_ecum_remove(struct pci_dev *pdev)
{
    struct pcie_ecum_t *pmcdev = pci_get_drvdata(pdev);

    if (!pmcdev) {
        dev_warn(&pdev->dev, "No device data found");
        return;
    }
    dev_info(&pdev->dev, "Removing PCIe %s device", ECUM_DRV_NAME);

    /* 清理 CAN FD 控制器 */
    exit_xcan_ctrl(&pmcdev->xcanfd_ctrl, ECUM_CANFD_DEV_NUM);
    dev_info(pmcdev->dev, "Exit %s controller success", pmcdev->xcanfd_ctrl.name);

    /* 清理 CAN 控制器 */
    exit_xcan_ctrl(&pmcdev->xcan_ctrl, ECUM_CAN_DEV_NUM);
    dev_info(pmcdev->dev, "Exit %s controller success", pmcdev->xcan_ctrl.name);

    /* 清理 PCIe 设备 */
    exit_pcie_dev(pdev, &pmcdev->pcie_dev);
    dev_info(&pdev->dev, "Exit %s device success", pmcdev->pcie_dev.name);

    pci_set_drvdata(pdev, NULL);
    dev_info(&pdev->dev, "Remove PCIe %s device success", ECUM_DRV_NAME);
}

/* PCI 设备 ID 表 */
static const struct pci_device_id pcie_ecum_ids[] = {
    { PCI_DEVICE(0x594D, 0x0001) },     /* ecu module */
    { 0, }
};
MODULE_DEVICE_TABLE(pci, pcie_ecum_ids);

/* PCI ecu 驱动结构 */
struct pci_driver pcie_ecum_driver = {
    .name = ECUM_DRV_NAME,
    .id_table = pcie_ecum_ids,
    .probe = pcie_ecum_probe,
    .remove = pcie_ecum_remove,
};
